feat(checkout): plumb off-session card saving for yearly renewals (draft — needs consent UI)#265
feat(checkout): plumb off-session card saving for yearly renewals (draft — needs consent UI)#265kilbot wants to merge 3 commits into
Conversation
Forward the signed-in customer's Medusa JWT on the payment-collection and
payment-session calls so Medusa resolves `auth_context.actor_id` to the
customer and creates/links a Stripe account holder (cus_...) on the
PaymentIntent — instead of the throwaway "Guest" that Stripe invents for
publishable-key-only requests.
The store payment-sessions workflow only creates an account holder
`when("customer-id-exists")`, and `customer_id` is derived solely from the
request's auth context; today the storefront's medusaFetch sends only the
publishable key, so actor_id is empty and every sale shows as Guest.
getCustomer() in the route already validates the token against
/store/customers/me (which rejects an empty actor_id), so the forwarded
token is guaranteed to resolve to a real customer. A missing token degrades
to the previous guest behaviour rather than breaking checkout.
Scope: attach-customer only (bookkeeping/reconciliation). Saving the card
for off-session renewals is a separate follow-up.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 18 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
🚀 Preview: https://wcpos-m8t8i1ffr-wcpos.vercel.app |
De-duplicate the authToken -> Authorization header spread shared by
createPaymentCollection and createPaymentSession into one buildAuthHeaders()
helper. Behaviour-preserving (returns the same Bearer header, or {} when no
token); addresses a review nitpick.
Add the engineering seam that saves a buyer's card as a reusable, off-session Stripe payment method — the prerequisite for a future "click to renew" charge on the yearly plan. createPaymentSession now accepts an optional setup_future_usage, forwarded as the session `data`; Medusa's store payment-sessions route passes `data` into the Stripe provider's initiatePayment, where it lands on the PaymentIntent (verified against @medusajs/payment-stripe normalizePaymentIntentParameters). The route sets 'off_session' only when the buyer consented (saveCard), the plan is yearly (lifetime never renews), and it's the Stripe card provider (PayPal/BTCPay have no off-session-card concept). A saved card is only chargeable because the customer is attached (previous commit). DORMANT until a consent UI is added: no client sets saveCard yet, so today this changes nothing on the wire. The consent checkbox + mandate copy is a deliberate owner/legal decision and is intentionally NOT included here.
c31db0f to
9ac68ca
Compare
|
🚀 Preview: https://wcpos-jnl0aw2b3-wcpos.vercel.app |
There was a problem hiding this comment.
kilbot has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
Closing — superseded by the CustomerSession approach. This PR forced |
#283) Wire the storefront to Stripe's optional "save my card" checkbox for yearly card checkouts. For a yearly Stripe payment session the API route now also mints a Stripe CustomerSession (new medusa endpoint POST /store/carts/:id/customer-session, wcpos-medusa#65) and returns its `customer_session_client_secret`; the value is threaded response → checkout-client → PaymentStep → StripeProvider and passed to <Elements customerSessionClientSecret>. Stripe then renders its own checkbox with a compliant off-session mandate and only saves the card on explicit consent — we never force-save. - Yearly + Stripe only. Non-yearly / non-card / no Stripe customer / any error → null secret → no checkbox; checkout is never blocked. - Degrades gracefully before the medusa endpoint is deployed (404 → null). - Replaces the closed #265 (which forced setup_future_usage with no opt-out). Tests: route mints/gates the session (yearly / lifetime / non-Stripe); the medusa client returns/nulls the secret; full suite + build green.
What this is
The engineering seam for Level 2 (save the card), building on #264 (attach a Stripe Customer). It lets a yearly buyer's card be saved as a reusable, off-session Stripe payment method — the prerequisite for a future "click to renew" charge.
How it works
createPaymentSessiongains an optionalsetupFutureUsage, forwarded as the sessiondata.dataverbatim into the Stripe provider'sinitiatePayment, wherenormalizePaymentIntentParameterscopiessetup_future_usageonto the PaymentIntent (verified in@medusajs/payment-stripesource).'off_session'only when all three hold: the buyer consented (saveCard), the plan is yearly (lifetime never renews), and it's the Stripe card provider (PayPal/BTCPay have no off-session-card concept).No client sets
saveCardyet, so on the wire this changes nothing today. I deliberately stopped at the engineering boundary and did not build the consent UI, because it's a legal/UX decision that's yours:To activate: add the consent checkbox, thread its value as
saveCardthrough thecomplete-cartclient wrapper → this route. The seam is ready and tested.Validation
vitest run— 1161/1161 pass (added:setup_future_usagethreading; yearly-only / Stripe-only / consent-absent / lifetime gating)eslint,tsc --noEmit,next build— all cleanNot in scope
Level 3 renewal-charge flow; the consent UI (see above); the Q2 order↔customer-linkage question flagged in #264.
🤖 Generated with Claude Code